package cn.yogehaoren.badfox.http.okhttp;


import cn.yogehaoren.badfox.core.BadFoxDownloader;
import cn.yogehaoren.badfox.http.BadFoxCookie;

import cn.yogehaoren.badfox.struct.BodyType;
import cn.yogehaoren.badfox.struct.CrawlerModel;
import cn.yogehaoren.badfox.struct.Request;
import cn.yogehaoren.badfox.struct.Response;
import cn.yogehaoren.badfox.utils.StrFormatUtil;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.ResponseBody;
import org.apache.commons.lang3.StringUtils;
import org.seimicrawler.xpath.JXDocument;
import org.seimicrawler.xpath.exception.XpathSyntaxErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 */
public class OkHttpDownloader implements BadFoxDownloader {

    public OkHttpDownloader(CrawlerModel crawlerModel){
        this.crawlerModel = crawlerModel;
    }

    private CrawlerModel crawlerModel;
    private Request currentRequest;
    private okhttp3.Request.Builder currentRequestBuilder;
    private OkHttpClient okHttpClient;
    private okhttp3.Response lastResponse;

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public Response process(Request request) throws Exception {
        currentRequest = request;
        OkHttpClient.Builder hcBuilder = OkHttpClientBuilderProvider.getInstance();
        if (crawlerModel.isUseCookie()){
            hcBuilder.cookieJar(crawlerModel.getOkHttpCookiesManager());
            addCookies(request.getUrl(),request.getCookies());
        }

        if(request.getProxy()!=null){
            hcBuilder.proxy(request.getProxy());
        }else if (crawlerModel.getStdProxy()!=null){
            hcBuilder.proxy(crawlerModel.getStdProxy());
        }

        hcBuilder.readTimeout(crawlerModel.getHttpTimeOut(), TimeUnit.MILLISECONDS);
        hcBuilder.connectTimeout(crawlerModel.getHttpTimeOut(),TimeUnit.MILLISECONDS);
        okHttpClient = hcBuilder.build();
        currentRequestBuilder = OkHttpRequestGenerator.getOkHttpRequesBuilder(request,crawlerModel);
        lastResponse = okHttpClient.newCall(currentRequestBuilder.build()).execute();
        return renderResponse(lastResponse,request);
    }

    @Override
    public Response metaRefresh(String nextUrl) throws Exception {
        HttpUrl lastUrl = lastResponse.request().url();
        if (!nextUrl.startsWith("http")){
            String prefix = lastUrl.scheme()+"://"+lastUrl.host()+lastUrl.encodedPath();
            nextUrl = prefix + nextUrl;
        }
        logger.info("BadFox refresh url to={} from={}",nextUrl,lastUrl.toString());
        currentRequestBuilder.url(nextUrl);
        lastResponse = okHttpClient.newCall(currentRequestBuilder.build()).execute();
        return renderResponse(lastResponse,currentRequest);
    }

    @Override
    public int statusCode() {
        return lastResponse.code();
    }

    @Override
    public void addCookies(String url, List<BadFoxCookie> badFoxCookies) {
        if (badFoxCookies ==null|| badFoxCookies.size()<=0){
            return;
        }
        for (BadFoxCookie badFoxCookie : badFoxCookies){
            Cookie.Builder cookieBuilder = new  Cookie.Builder();
            cookieBuilder.name(badFoxCookie.getName()).value(badFoxCookie.getValue())
            .path(StringUtils.isNotBlank(badFoxCookie.getPath())? badFoxCookie.getPath():"/")
            .domain(StringUtils.isNotBlank(badFoxCookie.getDomain())? badFoxCookie.getDomain(): StrFormatUtil.getDodmain(url));
            try {
                crawlerModel.getOkHttpCookiesManager().addCookie(new URI(url),cookieBuilder.build());
            } catch (URISyntaxException e) {
                logger.error(e.getMessage(),e);
            }
        }

    }

    private Response renderResponse(okhttp3.Response hcResponse, Request request){
        Response response = new Response();
        response.setRealUrl(lastResponse.request().url().toString());
        response.setUrl(request.getUrl());
        response.setRequest(request);
        response.setMeta(request.getMeta());
        response.setStatusCode(hcResponse.code());
        response.setReferer(hcResponse.header("Referer"));

        ResponseBody okResponseBody = hcResponse.body();
        try{
            if (okResponseBody!=null){
                String type = okResponseBody.contentType().type().toLowerCase();
                String subtype = okResponseBody.contentType().subtype().toLowerCase();
                if (type.contains("text")||type.contains("json")||type.contains("ajax")||subtype.contains("json")
                        ||subtype.contains("ajax")){
                    response.setBodyType(BodyType.TEXT);
                    try {
                        byte[] data = okResponseBody.bytes();
                        String utfContent = new String(data,"UTF-8");
                        String charsetFinal = renderRealCharset(utfContent);
                        if (charsetFinal.equals("UTF-8")){
                            response.setContent(utfContent);
                        }else {
                            response.setContent(new String(data,charsetFinal));
                        }
                    } catch (Exception e) {
                        logger.error("no content data");
                    }
                }else {
                    response.setBodyType(BodyType.BINARY);
                    try {
                        response.setData(okResponseBody.bytes());
                    } catch (Exception e) {
                        logger.error("no content data");
                    }
                }
            }
        }finally {
            hcResponse.close();
        }

        return response;
    }

    private String renderRealCharset(String content) throws XpathSyntaxErrorException {
        String charset;
        JXDocument doc = JXDocument.create(content);
        charset = StrFormatUtil.getFirstEmStr(doc.sel("//meta[@charset]/@charset"),"").trim();
        if (StringUtils.isBlank(charset)){
            charset = StrFormatUtil.getFirstEmStr(doc.sel("//meta[@http-equiv='charset']/@content"),"").trim();
        }
        if (StringUtils.isBlank(charset)){
            String ct = StringUtils.join(doc.sel("//meta[@http-equiv='Content-Type']/@content|//meta[@http-equiv='content-type']/@content"),";").trim();
            charset = StrFormatUtil.parseCharset(ct.toLowerCase());
        }
        return StringUtils.isNotBlank(charset)?charset:"UTF-8";
    }
}
